﻿using System;
using System.Net;
using System.Net.Sockets;

namespace NetTrafficStatistics
{
	///
	/// http://www.codeproject.com/KB/IP/CSNetworkSniffer.aspx
	/// 

	public enum Protocol
	{
		TCP = 6,
		UDP = 17,
		Unknown = -1
	};

	class Sniffer
	{
		private static Socket[] MainSocket;                          //The socket which captures all incoming packets
		private static byte[] ByteData = new byte[4096];

		internal static void Start ()
		{
			var name = Dns.GetHostName ();
			IPHostEntry hosyEntry = Dns.GetHostEntry (name);

			if (hosyEntry.AddressList.Length < 1)
				throw new Exception ("No device found on this machine");

			var i = 0;
			MainSocket = new Socket[hosyEntry.AddressList.Length];
			foreach (IPAddress ip in hosyEntry.AddressList)
			{
				//For sniffing the socket to capture the packets has to be a raw socket, with the
				//address family being of type internetwork, and protocol being IP
				MainSocket[i] = new Socket (AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);

				//Bind the socket to the selected IP address
				MainSocket[i].Bind (new IPEndPoint (IPAddress.Parse (ip.ToString()), 0));

				//Set the socket  options
				MainSocket[i].SetSocketOption (SocketOptionLevel.IP,            //Applies only to IP packets
				                               SocketOptionName.HeaderIncluded, //Set the include the header
				                               true);                           //option to true

				byte[] byTrue = new byte[] { 1, 0, 0, 0 };
				byte[] byOut = new byte[] { 1, 0, 0, 0 }; //Capture outgoing packets

				//Socket.IOControl is analogous to the WSAIoctl method of Winsock 2
				MainSocket[i].IOControl (IOControlCode.ReceiveAll,	//Equivalent to SIO_RCVALL constant of Winsock 2
				                         byTrue,
				                         byOut);

				//Start receiving the packets asynchronously
				MainSocket[i].BeginReceive (ByteData, 0, ByteData.Length, SocketFlags.None, new AsyncCallback (OnReceive), i);
				i++;
			}
		}

		public static void Stop ()
		{
			foreach (var socket in MainSocket)
			{
				socket.Close ();
			}
		}

		private static void OnReceive (IAsyncResult ar)
		{
			int i = (int) ar.AsyncState;
			try
			{
				var nReceived = MainSocket[i].EndReceive (ar);

				//Analyze the bytes received...
				IPHeader ipHeader = new IPHeader (ByteData, nReceived);
				var address = ((IPEndPoint) MainSocket[i].LocalEndPoint).Address;
				var srcIp = ipHeader.SourceAddress;
				var dstIp = ipHeader.DestinationAddress;
				var len = ipHeader.TotalLength;
				var ver = ipHeader.Version;
				string directing;

				if (address.Equals (srcIp))
				{
					directing = "output";
				}
				else if (address.Equals (dstIp))
				{
					directing = "input";
					dstIp = srcIp;
				}
				else
				{
					//Tools.WriteLogs (string.Format ("Unknown adresses:\tSource: {0}\tDestionation: {1}\tLength: {2}", srcIp, dstIp, len));
					return;
				}

				var zone = Tools.GetZone (address, dstIp, ver);
				SqLiteDb.Save (DateTime.Now, zone, directing, len);
			}
			catch (ObjectDisposedException)
			{
			}
			catch (Exception e)
			{
				Tools.WriteLogs (e.ToString());
			}
			finally
			{
				ByteData = new byte[4096];
				//Another call to BeginReceive so that we continue to receive the incoming
				//packets
				try
				{
					MainSocket[i].BeginReceive (ByteData, 0, ByteData.Length, SocketFlags.None, new AsyncCallback (OnReceive), i);
				}
				catch
				{
					// exit signal
				}
			}
		}
	}
}